package com.im.tcp.reciver;

import com.alibaba.fastjson.JSONObject;
import com.im.codec.proto.MessagePack;
import com.im.common.ClientType;
import com.im.tcp.config.TcpConfig;
import com.im.common.constant.Constants;
import com.im.common.enums.DeviceMultiLoginEnum;
import com.im.common.enums.command.SystemCommand;
import com.im.common.model.UserClientDto;
import com.im.tcp.utils.SessionSocketHolder;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.api.listener.MessageListener;

import java.util.List;

/**
 * 监听redis消息，处理用户登录的多端同步功能
 * <p>
 * 实现逻辑：
 * 1. 单端登录：如果用户只在一个端口登录，踢掉除本clinetType + imei外的其他设备。
 * 2. 双端登录：允许pc/mobile 其中一端登录 + web端，踢掉除了本clinetType + imei以外的web端设备。
 * 3. 三端登录：允许手机 + pc + web，踢掉同端的其他imei除了web。
 * 4. 不做任何处理。
 * <p>
 * 多端同步实现：
 * 当用户登录成功后，将用户信息封装成UserClientDto对象，通过Redisson的RTopic发布到Redis的UserLoginChannel频道中，
 * 然后通过订阅该频道的其他节点将用户登录信息同步到其他节点上。
 */
@Slf4j
public class UserLoginMessageListener {

    private RedissonClient redissonClient;
    private TcpConfig nettyConfig;


    public UserLoginMessageListener(RedissonClient redissonClient, TcpConfig nettyConfig) {
        this.redissonClient = redissonClient;
        this.nettyConfig = nettyConfig;
    }

    // 监听用户登录消息
    public void listenerUserLogin() {
        // 获取 RedissonClient 中指定主题的 RTopic 实例
        RTopic topic = redissonClient.getTopic(Constants.RedisConstants.UserLoginChannel);
        // 给 RTopic 实例添加监听器
        topic.addListener(String.class, new MessageListener<String>() {
            // 实现 onMessage 方法
            @Override
            public void onMessage(CharSequence charSequence, String msg) {
                log.info("收到用户上线通知：" + msg);
                // 解析收到的用户上线通知消息
                UserClientDto dto = JSONObject.parseObject(msg, UserClientDto.class);
                // 获取用户的所有 NioSocketChannel
                List<NioSocketChannel> nioSocketChannels = SessionSocketHolder.get(dto.getAppId(), dto.getUserId());
                Integer loginModel = nettyConfig.getLoginModel();
                // 遍历用户的每个 NioSocketChannel
                for (NioSocketChannel nioSocketChannel : nioSocketChannels) {
                    // 判断多端登录模式
                    if (loginModel == DeviceMultiLoginEnum.ONE.getLoginMode()) { // 模式一：覆盖登录
                        // 获取该 NioSocketChannel 对应的客户端类型和 IMEI 号
                        Integer clientType = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.ClientType)).get();
                        String imei = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.Imei)).get();

                        // 判断是否与收到的通知消息中的客户端类型和 IMEI 号相同
                        if (!(clientType + ":" + imei).equals(dto.getClientType() + ":" + dto.getImei())) {
                            // 如果不相同，就向该客户端发送 MutualLogin 命令
                            MessagePack<Object> pack = new MessagePack<>();
                            pack.setToId((String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get());
                            pack.setUserId((String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get());
                            pack.setCommand(SystemCommand.MUTUALLOGIN.getCommand());
                            nioSocketChannel.writeAndFlush(pack);
                        }

                    } else if (loginModel == DeviceMultiLoginEnum.TWO.getLoginMode()) { // 模式二：互踢登录
                        // 如果收到的通知消息中的客户端类型是 Web，就忽略该客户端
                        if (dto.getClientType() == ClientType.WEB.getCode()) {
                            continue;
                        }
                        // 获取该 NioSocketChannel 对应的客户端类型
                        Integer clientType = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.ClientType)).get();

                        // 如果该客户端类型是 Web，就忽略该客户端
                        if (clientType == ClientType.WEB.getCode()) {
                            continue;
                        }
                        // 获取该 NioSocketChannel 对应的 IMEI 号
                        String imei = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.Imei)).get();
                        // 判断是否与收到的通知消息中的客户端类型和 IMEI 号相同
                        if (!(clientType + ":" + imei).equals(dto.getClientType() + ":" + dto.getImei())) {
                            // 如果设备号和客户端类型不相同，发送互踢消息
                            MessagePack<Object> pack = new MessagePack<>();
                            pack.setToId((String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get());
                            pack.setUserId((String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get());
                            pack.setCommand(SystemCommand.MUTUALLOGIN.getCommand());
                            nioSocketChannel.writeAndFlush(pack);
                        }
                    } else if (loginModel == DeviceMultiLoginEnum.THREE.getLoginMode()) { // 模式三：不同设备多端登录
                        // 获取当前连接的设备类型和设备标识
                        Integer clientType = (Integer) nioSocketChannel.attr(AttributeKey.valueOf(Constants.ClientType)).get();
                        String imei = (String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.Imei)).get();

                        // 如果上线通知的客户端类型为Web，跳过该连接
                        if (dto.getClientType() == ClientType.WEB.getCode()) {
                            continue;
                        }

                        // 判断是否为同一类型的客户端
                        Boolean isSameClient = false;
                        if ((clientType == ClientType.IOS.getCode() || clientType == ClientType.ANDROID.getCode()) && (dto.getClientType() == ClientType.IOS.getCode() || dto.getClientType() == ClientType.ANDROID.getCode())) {
                            isSameClient = true;
                        }
                        if ((clientType == ClientType.MAC.getCode() || clientType == ClientType.WINDOWS.getCode()) && (dto.getClientType() == ClientType.MAC.getCode() || dto.getClientType() == ClientType.WINDOWS.getCode())) {
                            isSameClient = true;
                        }

                        // 如果是同一类型的客户端，但设备标识不同，向该连接发送下线命令
                        if (isSameClient && !(clientType + ":" + imei).equals(dto.getClientType() + ":" + dto.getImei())) {
                            MessagePack<Object> pack = new MessagePack<>();
                            pack.setToId((String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get());
                            pack.setUserId((String) nioSocketChannel.attr(AttributeKey.valueOf(Constants.UserId)).get());
                            pack.setCommand(SystemCommand.MUTUALLOGIN.getCommand());
                            nioSocketChannel.writeAndFlush(pack);
                        }
                    }
                }
            }
        });
    }
}